WaveFileReader.readFactChunk_   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileReader class.
27
 * @see https://github.com/rochars/wavefile-reader
28
 */
29
30
/** @module wavefile-reader */
31
32
import { RIFFFile } from 'riff-file';
33
import { unpackString, unpack } from 'byte-data';
34
35
/**
36
 * A class to read wav files.
37
 * @extends RIFFFile
38
 */
39
export class WaveFileReader extends RIFFFile {
40
41
  /**
42
   * @param {?Uint8Array=} wavBuffer A wave file buffer.
43
   * @param {boolean=} loadSamples True if the samples should be loaded.
44
   * @throws {Error} If container is not RIFF, RIFX or RF64.
45
   * @throws {Error} If format is not WAVE.
46
   * @throws {Error} If no 'fmt ' chunk is found.
47
   * @throws {Error} If no 'data' chunk is found.
48
   */
49
  constructor(wavBuffer=null, loadSamples=true) {
50
    super();
51
    // Include 'RF64' as a supported container format
52
    this.supported_containers.push('RF64');
53
    /**
54
     * The data of the 'fmt' chunk.
55
     * @type {!Object<string, *>}
56
     */
57
    this.fmt = {
58
      /** @type {string} */
59
      chunkId: '',
60
      /** @type {number} */
61
      chunkSize: 0,
62
      /** @type {number} */
63
      audioFormat: 0,
64
      /** @type {number} */
65
      numChannels: 0,
66
      /** @type {number} */
67
      sampleRate: 0,
68
      /** @type {number} */
69
      byteRate: 0,
70
      /** @type {number} */
71
      blockAlign: 0,
72
      /** @type {number} */
73
      bitsPerSample: 0,
74
      /** @type {number} */
75
      cbSize: 0,
76
      /** @type {number} */
77
      validBitsPerSample: 0,
78
      /** @type {number} */
79
      dwChannelMask: 0,
80
      /**
81
       * 4 32-bit values representing a 128-bit ID
82
       * @type {!Array<number>}
83
       */
84
      subformat: []
85
    };
86
    /**
87
     * The data of the 'fact' chunk.
88
     * @type {!Object<string, *>}
89
     */
90
    this.fact = {
91
      /** @type {string} */
92
      chunkId: '',
93
      /** @type {number} */
94
      chunkSize: 0,
95
      /** @type {number} */
96
      dwSampleLength: 0
97
    };
98
    /**
99
     * The data of the 'cue ' chunk.
100
     * @type {!Object<string, *>}
101
     */
102
    this.cue = {
103
      /** @type {string} */
104
      chunkId: '',
105
      /** @type {number} */
106
      chunkSize: 0,
107
      /** @type {number} */
108
      dwCuePoints: 0,
109
      /** @type {!Array<!Object>} */
110
      points: [],
111
    };
112
    /**
113
     * The data of the 'smpl' chunk.
114
     * @type {!Object<string, *>}
115
     */
116
    this.smpl = {
117
      /** @type {string} */
118
      chunkId: '',
119
      /** @type {number} */
120
      chunkSize: 0,
121
      /** @type {number} */
122
      dwManufacturer: 0,
123
      /** @type {number} */
124
      dwProduct: 0,
125
      /** @type {number} */
126
      dwSamplePeriod: 0,
127
      /** @type {number} */
128
      dwMIDIUnityNote: 0,
129
      /** @type {number} */
130
      dwMIDIPitchFraction: 0,
131
      /** @type {number} */
132
      dwSMPTEFormat: 0,
133
      /** @type {number} */
134
      dwSMPTEOffset: 0,
135
      /** @type {number} */
136
      dwNumSampleLoops: 0,
137
      /** @type {number} */
138
      dwSamplerData: 0,
139
      /** @type {!Array<!Object>} */
140
      loops: []
141
    };
142
    /**
143
     * The data of the 'bext' chunk.
144
     * @type {!Object<string, *>}
145
     */
146
    this.bext = {
147
      /** @type {string} */
148
      chunkId: '',
149
      /** @type {number} */
150
      chunkSize: 0,
151
      /** @type {string} */
152
      description: '', //256
153
      /** @type {string} */
154
      originator: '', //32
155
      /** @type {string} */
156
      originatorReference: '', //32
157
      /** @type {string} */
158
      originationDate: '', //10
159
      /** @type {string} */
160
      originationTime: '', //8
161
      /**
162
       * 2 32-bit values, timeReference high and low
163
       * @type {!Array<number>}
164
       */
165
      timeReference: [0, 0],
166
      /** @type {number} */
167
      version: 0, //WORD
168
      /** @type {string} */
169
      UMID: '', // 64 chars
170
      /** @type {number} */
171
      loudnessValue: 0, //WORD
172
      /** @type {number} */
173
      loudnessRange: 0, //WORD
174
      /** @type {number} */
175
      maxTruePeakLevel: 0, //WORD
176
      /** @type {number} */
177
      maxMomentaryLoudness: 0, //WORD
178
      /** @type {number} */
179
      maxShortTermLoudness: 0, //WORD
180
      /** @type {string} */
181
      reserved: '', //180
182
      /** @type {string} */
183
      codingHistory: '' // string, unlimited
184
    };
185
    /**
186
     * The data of the 'ds64' chunk.
187
     * Used only with RF64 files.
188
     * @type {!Object<string, *>}
189
     */
190
    this.ds64 = {
191
      /** @type {string} */
192
      chunkId: '',
193
      /** @type {number} */
194
      chunkSize: 0,
195
      /** @type {number} */
196
      riffSizeHigh: 0, // DWORD
197
      /** @type {number} */
198
      riffSizeLow: 0, // DWORD
199
      /** @type {number} */
200
      dataSizeHigh: 0, // DWORD
201
      /** @type {number} */
202
      dataSizeLow: 0, // DWORD
203
      /** @type {number} */
204
      originationTime: 0, // DWORD
205
      /** @type {number} */
206
      sampleCountHigh: 0, // DWORD
207
      /** @type {number} */
208
      sampleCountLow: 0 // DWORD
209
      /** @type {number} */
210
      //'tableLength': 0, // DWORD
211
      /** @type {!Array<number>} */
212
      //'table': []
213
    };
214
    /**
215
     * The data of the 'data' chunk.
216
     * @type {!Object<string, *>}
217
     */
218
    this.data = {
219
      /** @type {string} */
220
      chunkId: '',
221
      /** @type {number} */
222
      chunkSize: 0,
223
      /** @type {!Uint8Array} */
224
      samples: new Uint8Array(0)
225
    };
226
    /**
227
     * The data of the 'LIST' chunks.
228
     * Each item in this list look like this:
229
     *  {
230
     *      chunkId: '',
231
     *      chunkSize: 0,
232
     *      format: '',
233
     *      subChunks: []
234
     *   }
235
     * @type {!Array<!Object>}
236
     */
237
    this.LIST = [];
238
    /**
239
     * The data of the 'junk' chunk.
240
     * @type {!Object<string, *>}
241
     */
242
    this.junk = {
243
      /** @type {string} */
244
      chunkId: '',
245
      /** @type {number} */
246
      chunkSize: 0,
247
      /** @type {!Array<number>} */
248
      chunkData: []
249
    };
250
    /**
251
     * @type {{
252
        be: boolean,
253
        bits: number,
254
        fp: boolean,
255
        signed: boolean
256
      }}
257
     * @protected
258
     */
259
    this.uInt16 = {
260
      bits: 16, be: false, signed: false, fp: false
261
    };
262
    // If a file was informed, read it
263
    if (wavBuffer) {
264
      this.fromBuffer(wavBuffer, loadSamples);
265
    }
266
  }
267
268
  /**
269
   * Set up the WaveFileReader object from a byte buffer.
270
   * @param {!Uint8Array} wavBuffer The buffer.
271
   * @param {boolean=} samples True if the samples should be loaded.
272
   * @throws {Error} If container is not RIFF, RIFX or RF64.
273
   * @throws {Error} If format is not WAVE.
274
   * @throws {Error} If no 'fmt ' chunk is found.
275
   * @throws {Error} If no 'data' chunk is found.
276
   */
277
  fromBuffer(wavBuffer, samples=true) {
278
    // Always should reset the chunks when reading from a buffer
279
    this.clearHeaders();
280
    this.setSignature(wavBuffer);
281
    this.uInt16.be = this.uInt32.be;
282
    if (this.format != 'WAVE') {
283
      throw Error('Could not find the "WAVE" format identifier');
284
    }
285
    this.readDs64Chunk_(wavBuffer);
286
    this.readFmtChunk_(wavBuffer);
287
    this.readFactChunk_(wavBuffer);
288
    this.readBextChunk_(wavBuffer);
289
    this.readCueChunk_(wavBuffer);
290
    this.readSmplChunk_(wavBuffer);
291
    this.readDataChunk_(wavBuffer, samples);
292
    this.readJunkChunk_(wavBuffer);
293
    this.readLISTChunk_(wavBuffer);
294
  }
295
296
  /**
297
   * Reset the chunks of the WaveFileReader instance.
298
   * @protected
299
   * @ignore
300
   */
301
  clearHeaders() {
302
    let tmpWav = new WaveFileReader();
303
    Object.assign(this.fmt, tmpWav.fmt);
304
    Object.assign(this.fact, tmpWav.fact);
305
    Object.assign(this.cue, tmpWav.cue);
306
    Object.assign(this.smpl, tmpWav.smpl);
307
    Object.assign(this.bext, tmpWav.bext);
308
    Object.assign(this.ds64, tmpWav.ds64);
309
    Object.assign(this.data, tmpWav.data);
310
    this.LIST = [];
311
    Object.assign(this.junk, tmpWav.junk);
312
  }
313
  
314
  /**
315
   * Read the 'fmt ' chunk of a wave file.
316
   * @param {!Uint8Array} buffer The wav file buffer.
317
   * @throws {Error} If no 'fmt ' chunk is found.
318
   * @private
319
   */
320
  readFmtChunk_(buffer) {
321
    /** @type {?Object} */
322
    let chunk = this.findChunk('fmt ');
323
    if (chunk) {
324
      this.head = chunk.chunkData.start;
325
      this.fmt.chunkId = chunk.chunkId;
326
      this.fmt.chunkSize = chunk.chunkSize;
327
      this.fmt.audioFormat = this.readUInt16_(buffer);
328
      this.fmt.numChannels = this.readUInt16_(buffer);
329
      this.fmt.sampleRate = this.readUInt32(buffer);
330
      this.fmt.byteRate = this.readUInt32(buffer);
331
      this.fmt.blockAlign = this.readUInt16_(buffer);
332
      this.fmt.bitsPerSample = this.readUInt16_(buffer);
333
      this.readFmtExtension_(buffer);
334
    } else {
335
      throw Error('Could not find the "fmt " chunk');
336
    }
337
  }
338
339
  /**
340
   * Read the 'fmt ' chunk extension.
341
   * @param {!Uint8Array} buffer The wav file buffer.
342
   * @private
343
   */
344
  readFmtExtension_(buffer) {
345
    if (this.fmt.chunkSize > 16) {
346
      this.fmt.cbSize = this.readUInt16_(buffer);
347
      if (this.fmt.chunkSize > 18) {
348
        this.fmt.validBitsPerSample = this.readUInt16_(buffer);
349
        if (this.fmt.chunkSize > 20) {
350
          this.fmt.dwChannelMask = this.readUInt32(buffer);
351
          this.fmt.subformat = [
352
            this.readUInt32(buffer),
353
            this.readUInt32(buffer),
354
            this.readUInt32(buffer),
355
            this.readUInt32(buffer)];
356
        }
357
      }
358
    }
359
  }
360
361
  /**
362
   * Read the 'fact' chunk of a wav file.
363
   * @param {!Uint8Array} buffer The wav file buffer.
364
   * @private
365
   */
366
  readFactChunk_(buffer) {
367
    /** @type {?Object} */
368
    let chunk = this.findChunk('fact');
369
    if (chunk) {
370
      this.head = chunk.chunkData.start;
371
      this.fact.chunkId = chunk.chunkId;
372
      this.fact.chunkSize = chunk.chunkSize;
373
      this.fact.dwSampleLength = this.readUInt32(buffer);
374
    }
375
  }
376
377
  /**
378
   * Read the 'cue ' chunk of a wave file.
379
   * @param {!Uint8Array} buffer The wav file buffer.
380
   * @private
381
   */
382
  readCueChunk_(buffer) {
383
    /** @type {?Object} */
384
    let chunk = this.findChunk('cue ');
385
    if (chunk) {
386
      this.head = chunk.chunkData.start;
387
      this.cue.chunkId = chunk.chunkId;
388
      this.cue.chunkSize = chunk.chunkSize;
389
      this.cue.dwCuePoints = this.readUInt32(buffer);
390
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
391
        this.cue.points.push({
392
          dwName: this.readUInt32(buffer),
393
          dwPosition: this.readUInt32(buffer),
394
          fccChunk: this.readString(buffer, 4),
395
          dwChunkStart: this.readUInt32(buffer),
396
          dwBlockStart: this.readUInt32(buffer),
397
          dwSampleOffset: this.readUInt32(buffer),
398
        });
399
      }
400
    }
401
  }
402
403
  /**
404
   * Read the 'smpl' chunk of a wave file.
405
   * @param {!Uint8Array} buffer The wav file buffer.
406
   * @private
407
   */
408
  readSmplChunk_(buffer) {
409
    /** @type {?Object} */
410
    let chunk = this.findChunk('smpl');
411
    if (chunk) {
412
      this.head = chunk.chunkData.start;
413
      this.smpl.chunkId = chunk.chunkId;
414
      this.smpl.chunkSize = chunk.chunkSize;
415
      this.smpl.dwManufacturer = this.readUInt32(buffer);
416
      this.smpl.dwProduct = this.readUInt32(buffer);
417
      this.smpl.dwSamplePeriod = this.readUInt32(buffer);
418
      this.smpl.dwMIDIUnityNote = this.readUInt32(buffer);
419
      this.smpl.dwMIDIPitchFraction = this.readUInt32(buffer);
420
      this.smpl.dwSMPTEFormat = this.readUInt32(buffer);
421
      this.smpl.dwSMPTEOffset = this.readUInt32(buffer);
422
      this.smpl.dwNumSampleLoops = this.readUInt32(buffer);
423
      this.smpl.dwSamplerData = this.readUInt32(buffer);
424
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
425
        this.smpl.loops.push({
426
          dwName: this.readUInt32(buffer),
427
          dwType: this.readUInt32(buffer),
428
          dwStart: this.readUInt32(buffer),
429
          dwEnd: this.readUInt32(buffer),
430
          dwFraction: this.readUInt32(buffer),
431
          dwPlayCount: this.readUInt32(buffer),
432
        });
433
      }
434
    }
435
  }
436
437
  /**
438
   * Read the 'data' chunk of a wave file.
439
   * @param {!Uint8Array} buffer The wav file buffer.
440
   * @param {boolean} samples True if the samples should be loaded.
441
   * @throws {Error} If no 'data' chunk is found.
442
   * @private
443
   */
444
  readDataChunk_(buffer, samples) {
445
    /** @type {?Object} */
446
    let chunk = this.findChunk('data');
447
    if (chunk) {
448
      this.data.chunkId = 'data';
449
      this.data.chunkSize = chunk.chunkSize;
450
      if (samples) {
451
        this.data.samples = buffer.slice(
452
          chunk.chunkData.start,
453
          chunk.chunkData.end);
454
      }
455
    } else {
456
      throw Error('Could not find the "data" chunk');
457
    }
458
  }
459
460
  /**
461
   * Read the 'bext' chunk of a wav file.
462
   * @param {!Uint8Array} buffer The wav file buffer.
463
   * @private
464
   */
465
  readBextChunk_(buffer) {
466
    /** @type {?Object} */
467
    let chunk = this.findChunk('bext');
468
    if (chunk) {
469
      this.head = chunk.chunkData.start;
470
      this.bext.chunkId = chunk.chunkId;
471
      this.bext.chunkSize = chunk.chunkSize;
472
      this.bext.description = this.readString(buffer, 256);
473
      this.bext.originator = this.readString(buffer, 32);
474
      this.bext.originatorReference = this.readString(buffer, 32);
475
      this.bext.originationDate = this.readString(buffer, 10);
476
      this.bext.originationTime = this.readString(buffer, 8);
477
      this.bext.timeReference = [
478
        this.readUInt32(buffer),
479
        this.readUInt32(buffer)];
480
      this.bext.version = this.readUInt16_(buffer);
481
      this.bext.UMID = this.readString(buffer, 64);
482
      this.bext.loudnessValue = this.readUInt16_(buffer);
483
      this.bext.loudnessRange = this.readUInt16_(buffer);
484
      this.bext.maxTruePeakLevel = this.readUInt16_(buffer);
485
      this.bext.maxMomentaryLoudness = this.readUInt16_(buffer);
486
      this.bext.maxShortTermLoudness = this.readUInt16_(buffer);
487
      this.bext.reserved = this.readString(buffer, 180);
488
      this.bext.codingHistory = this.readString(
489
        buffer, this.bext.chunkSize - 602);
490
    }
491
  }
492
493
  /**
494
   * Read the 'ds64' chunk of a wave file.
495
   * @param {!Uint8Array} buffer The wav file buffer.
496
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
497
   * @private
498
   */
499
  readDs64Chunk_(buffer) {
500
    /** @type {?Object} */
501
    let chunk = this.findChunk('ds64');
502
    if (chunk) {
503
      this.head = chunk.chunkData.start;
504
      this.ds64.chunkId = chunk.chunkId;
505
      this.ds64.chunkSize = chunk.chunkSize;
506
      this.ds64.riffSizeHigh = this.readUInt32(buffer);
507
      this.ds64.riffSizeLow = this.readUInt32(buffer);
508
      this.ds64.dataSizeHigh = this.readUInt32(buffer);
509
      this.ds64.dataSizeLow = this.readUInt32(buffer);
510
      this.ds64.originationTime = this.readUInt32(buffer);
511
      this.ds64.sampleCountHigh = this.readUInt32(buffer);
512
      this.ds64.sampleCountLow = this.readUInt32(buffer);
513
      //if (wav.ds64.chunkSize > 28) {
514
      //  wav.ds64.tableLength = unpack(
515
      //    chunkData.slice(28, 32), uInt32_);
516
      //  wav.ds64.table = chunkData.slice(
517
      //     32, 32 + wav.ds64.tableLength);
518
      //}
519
    } else {
520
      if (this.container == 'RF64') {
521
        throw Error('Could not find the "ds64" chunk');
522
      }
523
    }
524
  }
525
526
  /**
527
   * Read the 'LIST' chunks of a wave file.
528
   * @param {!Uint8Array} buffer The wav file buffer.
529
   * @private
530
   */
531
  readLISTChunk_(buffer) {
532
    /** @type {?Object} */
533
    let listChunks = this.findChunk('LIST', true);
534
    if (listChunks !== null) {
535
      for (let j=0; j < listChunks.length; j++) {
536
        /** @type {!Object} */
537
        let subChunk = listChunks[j];
538
        this.LIST.push({
539
          chunkId: subChunk.chunkId,
540
          chunkSize: subChunk.chunkSize,
541
          format: subChunk.format,
542
          subChunks: []});
543
        for (let x=0; x<subChunk.subChunks.length; x++) {
544
          this.readLISTSubChunks_(subChunk.subChunks[x],
545
            subChunk.format, buffer);
546
        }
547
      }
548
    }
549
  }
550
551
  /**
552
   * Read the sub chunks of a 'LIST' chunk.
553
   * @param {!Object} subChunk The 'LIST' subchunks.
554
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
555
   * @param {!Uint8Array} buffer The wav file buffer.
556
   * @private
557
   */
558
  readLISTSubChunks_(subChunk, format, buffer) {
559
    if (format == 'adtl') {
560
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
561
        this.head = subChunk.chunkData.start;
562
        /** @type {!Object<string, string|number>} */
563
        let item = {
564
          chunkId: subChunk.chunkId,
565
          chunkSize: subChunk.chunkSize,
566
          dwName: this.readUInt32(buffer)
567
        };
568
        if (subChunk.chunkId == 'ltxt') {
569
          item.dwSampleLength = this.readUInt32(buffer);
570
          item.dwPurposeID = this.readUInt32(buffer);
571
          item.dwCountry = this.readUInt16_(buffer);
572
          item.dwLanguage = this.readUInt16_(buffer);
573
          item.dwDialect = this.readUInt16_(buffer);
574
          item.dwCodePage = this.readUInt16_(buffer);
575
        }
576
        item.value = this.readZSTR_(buffer, this.head);
577
        this.LIST[this.LIST.length - 1].subChunks.push(item);
578
      }
579
    // RIFF INFO tags like ICRD, ISFT, ICMT
580
    } else if(format == 'INFO') {
581
      this.head = subChunk.chunkData.start;
582
      this.LIST[this.LIST.length - 1].subChunks.push({
583
        chunkId: subChunk.chunkId,
584
        chunkSize: subChunk.chunkSize,
585
        value: this.readZSTR_(buffer, this.head)
586
      });
587
    }
588
  }
589
590
  /**
591
   * Read the 'junk' chunk of a wave file.
592
   * @param {!Uint8Array} buffer The wav file buffer.
593
   * @private
594
   */
595
  readJunkChunk_(buffer) {
596
    /** @type {?Object} */
597
    let chunk = this.findChunk('junk');
598
    if (chunk) {
599
      this.junk = {
600
        chunkId: chunk.chunkId,
601
        chunkSize: chunk.chunkSize,
602
        chunkData: [].slice.call(buffer.slice(
603
          chunk.chunkData.start,
604
          chunk.chunkData.end))
605
      };
606
    }
607
  }
608
609
  /**
610
   * Read bytes as a ZSTR string.
611
   * @param {!Uint8Array} bytes The bytes.
612
   * @param {number} index the index to start reading.
613
   * @return {string} The string.
614
   * @private
615
   */
616
  readZSTR_(bytes, index=0) {
617
    for (let i = index; i < bytes.length; i++) {
618
      this.head++;
619
      if (bytes[i] === 0) {
620
        break;
621
      }
622
    }
623
    return unpackString(bytes, index, this.head - 1);
624
  }
625
626
  /**
627
   * Read a number from a chunk.
628
   * @param {!Uint8Array} bytes The chunk bytes.
629
   * @return {number} The number.
630
   * @private
631
   */
632
  readUInt16_(bytes) {
633
    /** @type {number} */
634
    let value = unpack(bytes, this.uInt16, this.head);
635
    this.head += 2;
636
    return value;
637
  }
638
}
639